﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using PSIEpics;
using epics_server_remoting_class;
using string_short;
using System.Threading;
using System.IO;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting;
using System.Security.Permissions;
//using EJServer_driver;
using System.Text.RegularExpressions;
using JtextEpicsInterface;
using System.Reflection;

//this is the EpicsJecektServer lib
//suppose to run on windows and linux with mono 
//it does not come along with any hard ware code


//you will notice that all message that send to the server is differed from byte to string
//but the message server sent to client or driver sent to server is all byte
//couse not matter the message is byte or string it is processd by driver and sent to server in byte
//and server dont need to proccess it again before sent to the client 
//but in scanner when  scanner fills the pv is will have to make byte in to string to understand the message
//once the server start it's posible to stop and restart it(not implied yet)
//but it's strongly recommond to dispoes the server and creat a new one
//Note, there is a thing may be confusing the message send out to the driver is string or byte[](no matter if you are using plugin)
//but the message come from the hardware even procceced by plugin,is  byte[] ony  if you are using string message then simplly  use ascii encoding to convert it to string

namespace EpicsJecket
{
    /// <summary>
    /// This handle the message device that want to connect to the epics network.
    /// <para>It can get speciefic values from message and put in to PVs,and EJClient can send message to this ,and it will send it to the device Vie EJDriver.</para>
    /// 	<para>the message is divided into to type ,byte,and string.most of the time they are the same ,the different is in driver they may have different ways of handle them.
    /// the biggest different is in fill PV with the values in this message.byte use the positon in the byte[] to indicate where the value is.and string use the index of the sub string,that divided  from the message by spliter.</para>
    /// 	<para>for message that comes from the device to the server ,it's raw byte[],if it's a string message in PV Filling,if will be convert to string using ascii encoding.
    /// couse no matter the message is byte or string it is processd by driver and sent to server in byte[]..and server dont need to proccess it again before sent to the client
    /// but in scanner when  scanner fills the pv is will have to make byte in to string to understand the message</para>
    /// 	<para>once the server start it's posible to stop and restart it,
    /// but it's strongly recommanded that to dispoes the server and creat a new one</para>
    /// 	<para>This server will manage EJDriver for you and automaticly aplly plugIn,and send out scanner message.</para>
    /// </summary>
    public class EJServer
    {
        /// <summary>
        /// the lenth limit
        /// </summary>
        const int MSG_LEN=2048;
        /// <summary>
        /// client name lenth limit
        /// </summary>
        const int CLIENT_NAME_LEN = 32;
        /// <summary>
        /// server use this client nameout when broad casting
        /// </summary>
        const string BROADCAST = "ALL"; //server use this client nameout when broad casting
        /// <summary>
        /// default spliter for string message
        /// </summary>
        const string SPLITER_D = ":";
        /// <summary>
        /// default scan-message send out interval
        /// </summary>
        const int SCANINTERVAL_D =10000;
        /// <summary>
        /// the basic server working interval, server will check if there is a messge arrived at this interval
        /// </summary>
        const int MSGINTERVAL_D = 500;
        /// <summary>
        /// The server send out a message tothe device ,then it will expect the device reply,and if no reply it will time out ,this is the default time out time
        /// </summary>
        const int WATCGDOGTIMEOUT_D = 5000;
        /// <summary>
        /// the default limit of the timeOut  times,if time out to many times  it will try restart the driver is set to 0 then will not restart the driver
        /// </summary>
        const int TIMEOUTTIMESLIMIT_D = 10; //the default limit of the timeOut  times
        /// <summary>
        /// if some one is waiting for reply,thenothers will not use the driver port to send message,or some one is using the port-couse it's multi threading-,it will wait,ultill time out.
        /// </summary>
        const int PORTTIMEOUT_D = 200;
        /// <summary>
        /// if port time out to many times ,this is the limit
        /// </summary>
        const int PORTTRYTIMES_D = 25;


        /// <summary>
        /// he remoting server
        /// </summary>
        EpicsServer_Remoting myRemoteserver;    //the remoting server
        /// <summary>
        /// use singleton design pattern ,this indecats if JEpicsServer has been created
        /// </summary>
        public static EJServer  myInstance = null;  //use singleton design pattern ,this indecats if JEpicsServer has been created

        /// <summary>
        /// the basic server working interval, server will check if there is a messge arrived at this interval,set before start server
        /// </summary>
        public  int MsgInterval;    //the time interval to check if there is a message to be send
        /// <summary>
        /// the interval that the server send out scan message,set before start server
        /// </summary>
        public int ScanInterval;   //the time interval to send a scan cmd
        /// <summary>
        /// The server send out a message to the device ,then it will expect the device reply,and if no reply it will time out
        /// </summary>
        public int WatchdogTimeout;
        /// <summary>
        /// if time out to many times  it will try restart the driver is set to 0 then will never restart the driver
        /// </summary>
        public int TimeOutTimesLimit;
        /// <summary>
        /// Do you want console output message for debug
        /// </summary>
        public bool ConsoleOut; //do i use console to out put message?
        //public object ScanCMD;  //the scan commond,byte or string
        /// <summary>
        /// if the cmd is in byte form or else in ascii string
        /// </summary>
        bool ByteCMD;   //if the cmd is in byte form or else in ascii string
        /// <summary>
        /// does every bit of one  byte in a message repersent a different PVs
        /// </summary>
        public bool isBit;
        /// <summary>
        /// the lenth of the message
        /// </summary>
        int MsgLen;
        /// <summary>
        /// the end mark of the message
        /// </summary>
        byte MsgEnd;

        /// <summary>
        /// the lock object to1 lock the1 port ,
        /// <para>course it's muli-thread so the may try to use the port at the same time ,so this object makes sure ther is no conflict</para>
        /// </summary>
        object PortLock; //the lock object to1 lock the1 port ,check document for details
        /// <summary>
        /// when messager send out a message where the reply should go,like to scanner(a scan message reply) or to client
        /// </summary>
        Reply_to MessageReplay; //when messager send out a message where the reply should go

        /// <summary>
        /// when many clients send messages to the server the client is kept in queue.
        /// </summary>
        Queue ClientList;   //the queue for the client names(sbyte)

        /// <summary>
        /// DrvDesc is the driver description object contain like port name or some thing to init the driver
        /// </summary>
        object MyDrvDesc;//DrvDesc is the driver description object contain like port name or some thing to init the driver
        /// <summary>
        /// the driver
        /// </summary>
        private IEJDriver MyEJDriver;
        /// <summary>
        /// the plugin
        /// </summary>
        private IEJPlugIn MyPlugIn;


        /// <summary>
        /// the byte array to store the message
        /// </summary>
        byte[] MSGByte;     //the byte array to store the message
        /// <summary>
        /// the string to store the message
        /// </summary>
        string MSGString;   //the string to store the message
        //short[] MSGShort;   //this is importent ,this hold the short arrary directly get from the array recode in epics server
        /// <summary>
        /// the client which send the message to the server
        /// </summary>
        sbyte[] ClientName;  //the client which send the message to the server
        /// <summary>
        /// Scan command(scan message),the server where send scan message to device to check the status of the device, a string or a byte[] for byte message.
        /// </summary>
        public object ScanCmd;
        /// <summary>
        /// in a string message the message is devided in to many parts by the spliter.
        /// </summary>
        public string Spliter;
        /// <summary>
        /// if the scanner did not get scan message reply this is provide
        /// </summary>
        public object ScanDefaultReply; //if the scanner time out this is provide

        byte[] MSGOUTByte;     
        string MSGOUTString;   
        //short[] MSGOUTShort;  
       
        //in PVs
        double MSG_IN_Req;  //if any one send a message
        double MSG_OUT_Req;  //if any one send a message
        double busy;    //is the server busy
        double MSG_out_req;     //if the server send out a message
        /// <summary>
        /// indecates if the hardware connected to this EJServer is ok.
        /// you can use the pv : ServerName + ":SERVER_STATE" to monitor it
        /// </summary>
        double Server_State;    //refer te PV_ServerState
        
        
        private bool DRV_ok;    //if the driver is ok
        private int timeOutTimes;   //how many times we have time out in a roll
        private bool Server_running;
        
        private int HeartBeatVal;

        #region pvname
        //pv names
        string PV_MSG_IN_Req;
        string PV_MSG_OUT_Req;
        string PV_busy;
        string PV_MSG_Len;  //the byte message lenth ,notice that this lenth is in  byte
        string PV_MSG_Len_OUT;
        string PV_MSG_IN;
        string PV_MSG_OUT;
        string PV_ClientName;
        string PV_ClientName_Out;
        string PV_ServerState;  //indecates if the hardware is ok
        string PV_HeartBeat;    //when server is runnning make a heartBeat to let others know you are alive

        #endregion

        #region threads

        Thread watchDogThread;
        Thread ThreadIniDrv;
        Thread ThreadReiniDrv;
        Timer MessagerTimer;
        Timer ScannerTimer;
        TimerCallback MessagerDelegate;
        TimerCallback ScannerDelegate;
        Thread HeartBearThread;
        #endregion

        /// <summary>
        /// Messager(back to the client) = 2, Scanner = 3, Trash = 1, Non = 0 
        /// </summary>
        public enum Reply_to { Messager = 2, Scanner = 3, Trash = 1, Non = 0 };
        private Reply_to Send_Reply_to;

        /// <summary>
        /// the table for scaner ,cintains scan varibles(message property)
        /// </summary>
        private ArrayList ScanTable;    //the table for scaner cintains scan varibles

        /// <summary>
        /// handles that  the server send message out via driver event
        /// <seealso cref="OnOutMessage"/>
        /// </summary>
        /// <param name="msg">The Message that is sendingout.</param>
        /// <param name="isByte">if set to <c>true</c> [is byte].</param>
        public delegate void OutMessageHandler(object msg, bool isByte,Reply_to From_who);
        /// <summary>
        /// when the server send message out via driver this event raised
        /// </summary>
        public event OutMessageHandler OnOutMessage;    //when the server send message out via driver this event raised

        /// <summary>
        /// Initializes a new instance of the <see cref="EJServer"/> class.
        /// </summary>
        /// <param name="ServerName">Name of the server.</param>
        /// <param name="isByte">if set to <c>true</c> then it's a byte message.</param>
        /// <param name="len">The meaaage lenth.if set to0 then it will use the ending mark, negtive num means driver will handle the lenth</param>
        /// <param name="end">The ending mark.only works if len==0</param>
        /// <param name="msgRplay">where The reply message sent to ?.</param>
        public EJServer(string ServerName,bool isByte,int len,byte end,Reply_to msgRplay  )  
        {
            //initial server ,pv,and other things here
            //if (myInstance != null)
            //{
            //    throw new System.Exception("No multi-instance in one program");
            //}
            //else
            //{

            #region ini+remoting
            try
            {
                // 创建一个IPC信道。
                IpcChannel channel = new IpcChannel();
                // 注册这个信道。
                System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel(channel);
                // 注册一个远程对象的客户端代理.
                System.Runtime.Remoting.WellKnownClientTypeEntry remoteType = new System.Runtime.Remoting.WellKnownClientTypeEntry(typeof(EpicsServer_Remoting), "ipc://TestChannel/EpicsServer_Remoting.rem");
                System.Runtime.Remoting.RemotingConfiguration.RegisterWellKnownClientType(remoteType);       
            }
            catch
            {
 
            }
            myRemoteserver = new EpicsServer_Remoting();
            #endregion

            myInstance = this;

            ByteCMD = isByte;
            MsgLen = len;
            MsgEnd = end;
            //isBit = Bit;
            MessageReplay = msgRplay;
            MsgInterval = MSGINTERVAL_D;   //default msgInterval youcan change this befor start the server
            ScanInterval = SCANINTERVAL_D;
            WatchdogTimeout = WATCGDOGTIMEOUT_D;
            Spliter = SPLITER_D;
            TimeOutTimesLimit = TIMEOUTTIMESLIMIT_D;
            ConsoleOut = false;

            PortLock = new object();
            ClientList = new Queue();
            ScanTable = new ArrayList();

            //set the drivers  message arrive delegate
            //MyEJDriver = new EJDriver(isByte, len, end, DrvDesc);
            //MyEJDriver.SendtoEJServer = new EJDriver.SendToEJServerHandler(ReplyMSG_Arrived);
            //now the driver will beload by reflect  
            MyEJDriver = null;
            MyPlugIn = null;
            Server_running = false;
            ScanDefaultReply = null;
            ScanCmd = null;

            watchDogThread = new Thread(new ThreadStart(watchDog));
            MessagerDelegate = new TimerCallback(Messager);
            ScannerDelegate = new TimerCallback(Scanner);

            Server_State = -2;
            if (Server_running == true)
            {
                myRemoteserver.setRecord<double>(PV_ServerState, Server_State);
            }

            #region ini PV names and add PVs

            PV_busy = ServerName + ":BUSY";
            PV_ClientName = ServerName + ":CLIENTNAME";
            PV_MSG_IN = ServerName + ":MSG_IN";
            PV_MSG_IN_Req = ServerName + ":MSG_IN_REQ";
            PV_MSG_OUT = ServerName + ":MSG_OUT";
            PV_MSG_OUT_Req = ServerName + ":MSG_OUT_REQ";
            PV_ClientName_Out = ServerName + ":CLIENTNAMEOUT";
            PV_MSG_Len = ServerName + ":MSG_LEN";
            PV_MSG_Len_OUT = ServerName + ":MSG_LENOUT";
            PV_ServerState = ServerName + ":SERVER_STATE";
            PV_HeartBeat = ServerName + ":HEARTBEAT";
            #endregion

            Send_Reply_to = Reply_to.Non;
            //}

        }


        #region Driver&plugin

        /// <summary>
        /// Loads the drivers.
        /// </summary>
        /// <param name="DRVFile">The DRV file.</param>
        /// <param name="DrvName">Name of the DRV.</param>
        /// <param name="DrvDesc">The DRV desc.DrvDesc is the driver description object contain like port name or some thing to init the driver</param>
        /// <returns></returns>
        public bool LoadDrivers(string DRVFile, string DrvName, object DrvDesc)///DrvDesc is the driver description object contain like port name or some thing to init the driver
        {
            MyDrvDesc = DrvDesc;
            Assembly DrvAsm = Assembly.LoadFile(DRVFile);   //extract the assembly ftom the file
            Type[] DrvTypes = DrvAsm.GetTypes();   //get the types in this assembly
            foreach (Type DrvType in DrvTypes)
            {
                if (IsDriver(DrvType) == true)
                {
                    IEJDriver  DrvInst = ((IEJDriver)DrvAsm.CreateInstance(DrvType.FullName));  //create driver instace
                    if (DrvInst.DriverName == DrvName)
                    {
                        MyEJDriver = DrvInst;
                        MyEJDriver.OnSendToEJServer += new SendToEJServerHandler(ReplyMSG_Arrived);
                        MyEJDriver.OnDriverCrash += new DriverCrashHandler(DriverCrashed);
                        return true;    //an EpicsJacket only need one driver;
                    }                    
                }
            }
            return false;
        }   
        protected static bool IsDriver(Type TDrv)
        {
            //return the Tdriv has the IJEDriver interface
            Type[] interfaces = TDrv.GetInterfaces();
            foreach (Type theInterface in interfaces)
            {
                if (theInterface.FullName == "JtextEpicsInterface.IEJDriver")
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Inis the driver.will keep trying untill the driver is ok
        /// </summary>
        protected  void IniDriver()   
        {
            if (MyEJDriver == null)
            {
                throw new System.Exception("No Driver");
                return ;
            }
            try
            {
                MyEJDriver.SetDriver(ByteCMD, MsgLen, MsgEnd, MyDrvDesc);
                //MyEJDriver.OnSendToEJServer+=new SendToEJServerHandler(ReplyMSG_Arrived);
                MyEJDriver.IniDriver();
                Server_State = -1;  //driver running port ok,
                if (Server_running == true)
                {
                    myRemoteserver.setRecord<double>(PV_ServerState, Server_State);
                }
            }
            catch
            {
                Server_State = -2;  //driver failed 
                if (Server_running == true)
                {
                    myRemoteserver.setRecord<double>(PV_ServerState, Server_State);
                }
                ConsoleOutput("Driver faled");
                ThreadReiniDrv = new Thread(new ThreadStart(ReIniDriver));
                ThreadReiniDrv.Start();
                return ;
            }
            DRV_ok = true;
            return ;
        }

        /// <summary>
        /// this will re-initialize the driver,if it crashes
        /// </summary>
        private void ReIniDriver()
        {
            Random ran = new Random();
            DRV_ok = false; //first stop others using the driver
            Thread.Sleep(20);  //then apply a delay
            MyEJDriver.StopDriver();    //then stop the driver
            int re_time = ran.Next(1000, 5000);
            ConsoleOutput("try to restart driver in "+re_time.ToString());  //then another long random delay to make sure not collesion 
            Thread.Sleep(re_time);  
            ThreadIniDrv = new Thread(new ThreadStart(IniDriver));  //the restart driver
            ThreadIniDrv.Start();
        }

        /// <summary>
        /// Load the Plugin that may modify the message
        /// </summary>
        /// <param name="filePath">The file path.</param>
        /// <returns></returns>
        public bool SetPlugin(string filePath)
        {//filepath is the plugin dll file path
            Assembly PluginAsm=null;
            try
            {
                PluginAsm = Assembly.LoadFile(filePath);   //extract the assembly ftom the file
            }
            catch
            {
                ConsoleOutput("No Plugin File");
                return false;
            }
            Type[] PlugTypes = PluginAsm.GetTypes();   //get the types in this assembly
            try
            {
                foreach (Type PlugType in PlugTypes)
                {
                    IEJPlugIn PlugInInst = ((IEJPlugIn)PluginAsm.CreateInstance(PlugType.FullName));  //create driver instace
                    MyPlugIn = PlugInInst;
                    ConsoleOutput("plug-in added" + MyPlugIn.PluginName);
                    return true;
                }
            }
            catch
            {
                ConsoleOutput("Not a Plugin File");
            }
            return false;
        }

        /// <summary>
        /// when Drivers the crashed.this will resrart the driver if Necessary,TimeOutTimesLimit>0
        /// </summary>
        private void DriverCrashed()
        {
            //the driver crash event raised ,i will restart the driver
            if (TimeOutTimesLimit <= 0)
            {
                return;
            }
            ThreadReiniDrv = new Thread(new ThreadStart(ReIniDriver));
            ThreadReiniDrv.Start();
        }


        #endregion



        /// <summary>
        /// Starts the server.
        /// <para>initialize all PVs,start listening to client message,start driver&plugin</para>
        /// </summary>
        public void StartServer()
        {
            //add pvs
            
            myRemoteserver.addRecord<double>(PV_busy, true);
            myRemoteserver.addRecord<double>(PV_MSG_IN_Req, true);
            myRemoteserver.addRecord<double>(PV_MSG_OUT_Req, true);
            myRemoteserver.addRecord<double>(PV_MSG_Len, true);
            myRemoteserver.addRecord<double>(PV_MSG_Len_OUT, true);
            myRemoteserver.addRecord<double>(PV_ServerState, true);
            myRemoteserver.addRecord<int>(PV_HeartBeat, true);

            myRemoteserver.addRecordArray<sbyte>(PV_MSG_IN, MSG_LEN, true);
            myRemoteserver.addRecordArray<sbyte>(PV_MSG_OUT, MSG_LEN, true);
            myRemoteserver.addRecordArray<sbyte>(PV_ClientName, CLIENT_NAME_LEN, true);    //the client name is shorter 
            myRemoteserver.addRecordArray<sbyte>(PV_ClientName_Out, CLIENT_NAME_LEN, true);
            Server_running = true;

            //start the thread here
            DRV_ok = false;
            //try
            //{
            this.IniDriver();
            //}
            //catch
            //{
            //    ConsoleOutput("Driver faled");        
            //    return; 
            //}

            if (MyPlugIn != null)
            {
                try
                {
                    MyPlugIn.iniPlugIn();
                }
                catch 
                {
                    MyPlugIn = null;
                    ConsoleOutput("PlugIn faled");        
                }
            }
                //start messager
            watchDogThread.Start();
            MessagerTimer = new Timer(MessagerDelegate, null, MsgInterval, MsgInterval);
            //start heart beat
            HeartBearThread = new Thread(new ThreadStart(HeartBeat));
            HeartBearThread.Start();
            //start scanner
            //this.StartScan();
        }

        /// <summary>
        /// Disposes the server.
        /// </summary>
        public void DisposeServer()
        {
            Server_running = false;
            //stop the treads
            watchDogThread.Abort();
            MessagerTimer.Dispose();
            ScannerTimer.Dispose();
            MyEJDriver.StopDriver();
            //dispose heartbeat thread,NO!! the heart beat will dispose by if self
            //dispose all PV records here
            foreach (MessageProp tSV in ScanTable)
            {
                myRemoteserver.disposeRecord<double>(tSV.pvName);
            }
            myRemoteserver.disposeRecord<double>(PV_busy);
            myRemoteserver.disposeRecord<double>(PV_MSG_IN_Req);
            myRemoteserver.disposeRecord<double>(PV_MSG_OUT_Req);
            myRemoteserver.disposeRecord<double>(PV_MSG_Len);
            myRemoteserver.disposeRecord<double>(PV_MSG_Len_OUT);
            myRemoteserver.disposeRecord<double>(PV_ServerState);
            myRemoteserver.disposeRecord<int>(PV_HeartBeat);

            myRemoteserver.disposeRecordArray<sbyte>(PV_MSG_IN);
            myRemoteserver.disposeRecordArray<sbyte>(PV_MSG_OUT);
            myRemoteserver.disposeRecordArray<sbyte>(PV_ClientName);    //the client name is shorter 
            myRemoteserver.disposeRecordArray<sbyte>(PV_ClientName_Out);

        }


        #region send 

        /// <summary>
        /// Out side the server ,you can used this function to send message to hardware using the .
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="ReplyTo">put the reply message to?? .</param>
        /// <returns></returns>
        public bool SendFromServer(object message, Reply_to ReplyTo)
        {
            if (DRV_ok == false)
            {
                return false;
            }
            int Try_times = 0;
            while (this.Set_Reply_To(ReplyTo) != 0)  //try to get the port
            {
                ConsoleOutput("Prot N/A");
                Thread.Sleep(PORTTIMEOUT_D);
                Try_times++;
                if (Try_times > PORTTRYTIMES_D)
                {
                    return false ;     //fell to send out msg to drv 
                }
            }
            ConsoleOutput("get port");   //test code
            if (ByteCMD == true)
            {
                //if byteCMD turn short to byte
                byte[] Msg;
                Msg = (byte[])message;
                if (MyPlugIn != null)
                {
                    Msg = MyPlugIn.ProcOut(Msg);        //if has plugin than use it
                }
                if (OnOutMessage != null)
                {
                    //let the out side konw the message
                    OnOutMessage(Msg, this.ByteCMD, MessageReplay);
                }
                MyEJDriver.Send_msg(Msg);
                return true;
            }
            else
            {
                string Msg;
                Msg = (string)message;
                if (MyPlugIn != null)
                {
                    Msg = MyPlugIn.ProcOut(Msg);
                }
                if (OnOutMessage != null)
                {
                    //let the out side konw the message
                    OnOutMessage(Msg, this.ByteCMD, MessageReplay);
                }
                MyEJDriver.Send_msg(Msg);
                return true;
            }
        }


        /// <summary>
        /// listening to the client ,when there is a message in handles it
        /// </summary>
        /// <param name="input_object">not used</param>
        private  void   Messager(object input_object)
        {
            //this function will watch any incoming messages and send it out 
            //interval controled by timer
            if (DRV_ok == false)
            {
                return;
            }
            int Try_times=0;
            if (myRemoteserver.getRecord<double>(PV_MSG_IN_Req) == 0)
            {
                //Console.Write(".");
                return ;
                
            }
            else
            {
                //message comes
                while  (this.Set_Reply_To(MessageReplay) != 0)  //try to get the port
                {
                    ConsoleOutput("Prot N/A");
                    Thread.Sleep(PORTTIMEOUT_D);
                    Try_times++;
                    if (Try_times > PORTTRYTIMES_D)
                    {
                        //force to ack the msg in req
                        myRemoteserver.setRecord<double>(PV_MSG_IN_Req, 0);     //ack
                        myRemoteserver.setRecord<double>(PV_busy, 0);       //ack
                        return;     //fell to send out msg to drv maybe should rise a event 
                    }
                }
                //sucessfully get the port
                ConsoleOutput("get port");   //test code
                ClientName = myRemoteserver.getRecordArray<sbyte>(PV_ClientName);
                ClientList.Enqueue(ClientName.Clone());
                if (ByteCMD == true)
                {
                    //if byteCMD turn short to byte
                    byte[] Msg;
                    Msg = this.GetMsgByte();
                    //reset busy and client name
                    myRemoteserver.setRecord<double>(PV_MSG_IN_Req, 0);     //ack
                    myRemoteserver.setRecord<double>(PV_busy, 0);       //ack
                    //ClientName = myRemoteserver.getRecordArray<sbyte>(PV_ClientName);
                    if (MyPlugIn != null)
                    {
                        Msg = MyPlugIn.ProcOut(Msg);        //if has plugin than use it
                    }
                    if (OnOutMessage != null)
                    {
                        //let the out side konw the message
                        OnOutMessage(Msg, this.ByteCMD, MessageReplay);
                    }
                    MyEJDriver.Send_msg(Msg);
                    return ;
                }
                else
                {
                    string  Msg;
                    Msg = this.GetMsgString();
                    //reset busy and client name
                    myRemoteserver.setRecord<double>(PV_MSG_IN_Req, 0); //ack
                    myRemoteserver.setRecord<double>(PV_busy, 0);       //ack
                    //ClientName = myRemoteserver.getRecordArray<sbyte>(PV_ClientName);
                    if (MyPlugIn != null)
                    {
                        Msg = MyPlugIn.ProcOut(Msg);
                    }
                    if (OnOutMessage != null)
                    {
                        //let the out side konw the message
                        OnOutMessage(Msg, this.ByteCMD,MessageReplay);
                    }
                    MyEJDriver.Send_msg(Msg);
                    return  ;
                }
            }
        }

        #endregion

        #region scan
        /// <summary>
        /// Starts the scanner.It will send ot scan message and handles the reply.
        /// </summary>
        /// <param name="interval">The scan interval.</param>
        public void StartScan(int interval)
        {
            ScanInterval = interval;
            StartScan();
        }

        /// <summary>
        /// Starts the scanner.It will send ot scan message and handles the reply.
        /// </summary>
        public void StartScan()
        {
            //start scanner thread
            if (ScanTable.Count <= 0||DRV_ok==false )
            {
                return; 
            }
            if (ScanInterval<=0)
            {
                return;
            }
            this.iniScaner();
            ScannerTimer = new Timer(ScannerDelegate,null,ScanInterval,ScanInterval);

        }


        private void  iniScaner()
        {
            //ini scan table
            foreach(MessageProp tSV in ScanTable )
            {
                myRemoteserver.addRecord<double>(tSV.pvName, true);

            }
        }

        /// <summary>
        /// Adds the scan varible=Add MessageProperty(A PV)
        /// <seealso cref="MessageProp"/>
        /// </summary>
        /// <param name="pvName">Name of the pv.</param>
        /// <param name="position">The position.If set to -1, it will be a dummy pv, will not be fill be reply ,message</param>
        /// <param name="offset">The offset.</param>
        /// <param name="bitPos">The bit pos.</param>
        /// <param name="thisShift">The this shift.</param>
        public void addScanVarible(string pvName, int position, int offset,short bitPos,int thisShift)
        {
            MessageProp t_sv = new MessageProp(pvName, position, offset, ByteCMD,bitPos, thisShift);
            ScanTable.Add(t_sv);
        }

        /// <summary>
        /// Adds the scan varible=Add MessageProperty(A PV),using a MessageProp to describe the MessagePoperty.
        ///  <seealso cref="MessageProp"/>
        /// </summary>
        /// <param name="scanVarible">The messageProp.</param>
        public void addScanVarible(MessageProp scanVarible)
        {
            ScanTable.Add(scanVarible);
        }

        /// <summary>
        /// Fills the PVs.this use the reply message to get values that will be put into PVs.
        /// </summary>
        /// <param name="CMD">The reply message.</param>
        private void fillPV(byte[] CMD)
        {
            //this fills the pv with 
            //scanner only support double
            if (ScannerTimer==null )
            {
                return;
            }
            byte ripper=1;
            if (ByteCMD == false)
            {
                string[] cmdArray;
                string Scmd;
                try
                {
                    Encoding my_ASC = Encoding.ASCII;
                    Scmd = my_ASC.GetString(CMD);
                    cmdArray = Regex.Split(Scmd, Spliter, RegexOptions.None);
                    ConsoleOutput("fill pv with :" + Scmd);
                }
                catch
                {
                    Encoding my_ASC = Encoding.ASCII;
                    Scmd = (string)ScanDefaultReply;
                    cmdArray = Regex.Split(Scmd, Spliter, RegexOptions.None);
                    ConsoleOutput("scaner replay wrong");   //testcode
                    ConsoleOutput("fill pv with :" + Scmd);
                }
                foreach (MessageProp tSV in ScanTable)
                {
                    if (tSV.Position == -1)
                    {
                        continue;
                    }
                    tSV.VAL = Convert.ToDouble(cmdArray[tSV.Position]);
                    myRemoteserver.setRecord<double>(tSV.pvName, (double)(tSV.VAL));
                }

            }
            else
            {
                byte[] Bcmd=(byte[])CMD;
                foreach (MessageProp tSV in ScanTable)
                {
                    if (tSV.Position == -1)
                    {
                        continue;
                    }
                    if (tSV.isBit>=0)   //you1have to rib a bit out of a byte
                    {
                        ripper=(byte)(ripper<<tSV.isBit) ;
                        tSV.VAL = (double) (Bcmd[tSV.Position + tSV.offset]&ripper);
                        myRemoteserver.setRecord<double>(tSV.pvName, (double)(tSV.VAL));
                    }
                    else
                    {

                         if (tSV.shift == 9)
                         {
                             double t_val = 0;
                             t_val = BitConverter.ToDouble(Bcmd, tSV.Position + tSV.offset);
                             //if you set shift to 9 this meas you have used a double data type and this is the only thing we accept along with intiger
                             //make sure you double is 64bit long and lower byte in front higher comes after it
                         }
                         else
                         {
                             UInt64 t_Val=0;
                             UInt64 tt_Val = 0;
                             for (int i = 0; i < System.Math.Abs(tSV.shift); i++)    //shift is how many byte youwant to shift and fuse into a double max is 8 min is 1
                             {
                                 tt_Val = (UInt64)Bcmd[tSV.Position + tSV.offset + i];//and only int is used although it's put into a double ,it;s still a iniger
                                 if (tSV.shift > 0)
                                 {
                                     //tt_Val = (UInt64)tSV.VAL;
                                     t_Val = t_Val | (tt_Val << (i * 8));   //lower bytes comes first
                                 }
                                 else
                                 {
                                     //tt_Val = (UInt64)tSV.VAL;
                                     t_Val = t_Val | (tt_Val << ((8 - i-1) * 8)); //higher byte come first
                                 }
                             }
                             myRemoteserver.setRecord<double>(tSV.pvName, (double)(t_Val)); //put together
                         }
                    }
   
                }
            }

        }

        /// <summary>
        ///this function will send scan cmd at a fixed interval
        ///interval controled by timer
        /// </summary>
        /// <param name="input_object">The input_object.</param>
        private  void Scanner(object input_object)
        {
            //this function will send scan cmd at a fixed interval
            //interval controled by timer
            if (DRV_ok == false)
                return;
            int Try_times = 0;
            while (this.Set_Reply_To(Reply_to.Scanner) != 0)  //try to get the port
            {
                ConsoleOutput("Scanner Port N/A");
                Thread.Sleep(PORTTIMEOUT_D);
                Try_times++;
                if (Try_times >PORTTRYTIMES_D)
                    return;     //fell to send out msg to drv maybe should rise a event 
            }
            ConsoleOutput("Scaner Sent");
            if (ByteCMD == true)
            {
                //if byteCMD turn short to byte
                byte[] Msg;
                Msg = (byte[])ScanCmd;
                MyEJDriver.Send_msg(Msg);
                OnOutMessage(Msg, this.ByteCMD, Reply_to.Scanner);
                return;
            }
            else
            {
                string Msg;
                Msg = (string)ScanCmd;
                MyEJDriver.Send_msg(Msg);
                OnOutMessage(Msg, this.ByteCMD, Reply_to.Scanner);
                return;
            }

        }

        #endregion



        #region reply
        /// <summary>
        /// this is registed to the MyEJDriver.OnSendToEJServer event;code: MyEJDriver.OnSendToEJServer+=new SendToEJServerHandler(ReplyMSG_Arrived);
        /// <para>this is public couse it alod you to call this to send message to the EJServer,with out driver</para>
        /// </summary>
        /// <param name="Msg">The message.</param>
        public void ReplyMSG_Arrived(byte[] Msg)        //this is registed to the MyEJDriver.OnSendToEJServer event;code: MyEJDriver.OnSendToEJServer+=new SendToEJServerHandler(ReplyMSG_Arrived);
        {

            //this will reset send_reply_to
            //an evnet handler to EJDriver's sendToEjserver
            //others who wants to input data to the EJServer can1 call this， that's why this is public 
            Server_State = 0;   //when i recieve mseeage from the hradware i think it's connected and working
            if (Server_running == true)
            {
                myRemoteserver.setRecord<double>(PV_ServerState, Server_State); 
            }
            timeOutTimes = 0;
            Reply_to tempReplyTo = Send_Reply_to;
            Send_Reply_to = Reply_to.Non;
            if (MyPlugIn != null)
            {
                Msg = MyPlugIn.ProcIn(Msg, ByteCMD, (int)tempReplyTo);//2 is to messager,3 is to scanner
            }
            switch (tempReplyTo)
            {
                case Reply_to.Messager:
                    {
                        //messager will sent the reply message to the client whom just sent some thing in

                        SendToClient(Msg,null );
                        break;
                    }
                case Reply_to.Scanner:
                    {
                        //scanner will have to understand the message
                        SendToScanner(Msg);
                        break;
                    }
                case Reply_to.Trash:
                    {   //just don't care
                        try
                        {
                            ClientList.Dequeue()   ;    //this should go back to the client but the client do not wat it
                        }
                        catch
                        {
                            //iganor the error
                        }
                        break;
                    }
               default :
                    {
                        try
                        {
                            ClientList.Dequeue();    //this should go back to the client but the client do not wat it
                        }
                        catch
                        {
                            //iganor the error
                        }
                        SendToClient(Msg,BROADCAST );
                        //unexpected message comes
                        break;
                    }
            }

        }

        /// <summary>
        ///call this befor using the driver port.  
        /// if returns 0 means everything is ok ready to send.  
        ///if not it means some one is wating for reply
        /// </summary>
        /// <param name="MyReplyTo">My reply to.</param>
        /// <returns></returns>
        private int Set_Reply_To(Reply_to MyReplyTo)
        {
            //if returns 0 means everything is ok ready to send
            //if not it means some one is wating for reply
            lock (PortLock)
            {
                if (Send_Reply_to == Reply_to.Non)
                {
                    Send_Reply_to = MyReplyTo;
                    return 0;
                }
                else
                {
                    return (int)Send_Reply_to;
                }
            }
        }

        /// <summary>
        /// Sends the reply to client.
        /// </summary>
        /// <param name="msg">The message.</param>
        /// <param name="ToClient">which client ,null=last client in CLient list.</param>
        private void SendToClient(byte[] msg,object ToClient)    //
        {
            myRemoteserver.setRecord<double>(PV_MSG_OUT_Req, 0);
            sbyte[] client;
            if (ToClient==null && ClientList.Count != 0)
            {
                //client in the clientList
                client = (sbyte[])(ClientList.Dequeue());
            }
            else if (ToClient == null)
            {
                client = Str_Short.StringToSByte(BROADCAST);
                ConsoleOutput("Broadcasting");
            }
            else
            {
                //you spacifiy a client
                client = Str_Short.StringToSByte((string)ToClient);
                ConsoleOutput("Broadcasting");
            }
            this.SetMsg(msg, client);
            myRemoteserver.setRecord<double>(PV_MSG_OUT_Req, 1);  
        }

        /// <summary>
        /// Sends reply to scanner.
        /// </summary>
        /// <param name="msg">The MSG.</param>
        private void SendToScanner(byte[] msg)
        {
            this.fillPV(msg);
            //the futrue scanner will have more function
        }


        #endregion



        /// <summary>
        /// if someone wait for the reply for too long the watchdog reset the "reply to" that no one is wait for reply
        /// </summary>
        private void watchDog()
        {       
            //if someone wait for the reply for too long the watchdog reset the "reply to" that no one is wait for reply
            Timer WatchDogTimer;
            Reply_to LastReplyTo;
            LastReplyTo = Reply_to.Non;
            WatchDogTimer = null;
            while (true)
            {
                if (Server_State == -2)
                {
                    continue;  //if the drivers' not work watch is not needed
                }
                if (WatchDogTimer == null)
                {
                    if (Send_Reply_to != Reply_to.Non && LastReplyTo == Send_Reply_to)
                    {
                        WatchDogTimer = new Timer(new TimerCallback(WatchDogTimeOut), null,WatchdogTimeout, WatchdogTimeout); ///watch timout const
                    }
                    
                }
                else
                {
                    if (Send_Reply_to == Reply_to.Non || LastReplyTo != Send_Reply_to)
                    {
                        WatchDogTimer.Dispose();
                        WatchDogTimer = null;
                    }
                }


                LastReplyTo = Send_Reply_to;
                Thread.Sleep(5);
                    
            }
        }

        /// <summary>
        /// Watches the dog time out.
        /// </summary>
        /// <param name="state">The state.</param>
        private void WatchDogTimeOut(object state)
        {
            Send_Reply_to = Reply_to.Non;       //the device did not reply in time
            ClientList.Clear(); //clear all client wait in queue
            try
            {
                timeOutTimes += 1;
                if (ScanDefaultReply != null)
                {
                    if (ByteCMD == true)
                    {
                        SendToScanner((byte[])ScanDefaultReply);    //if not reply used this to fill scanner
                        ConsoleOutput("Default Reply-B");
                    }
                    else
                    {
                        ConsoleOutput("Default Reply : " + ScanDefaultReply);
                        Encoding my_ASC = Encoding.ASCII;
                        byte[] b = my_ASC.GetBytes((string)ScanDefaultReply);
                        SendToScanner((byte[])b);
                        
                    }
                }
                Server_State = -1;
                if (Server_running == true)
                {
                    myRemoteserver.setRecord<double>(PV_ServerState, Server_State);
                }
                ConsoleOutput("watch dog timeout *"+ timeOutTimes.ToString());
                if (timeOutTimes > TimeOutTimesLimit && TimeOutTimesLimit>0)
                {
                    timeOutTimes = 0;    //if time out 10 times in a roll we resatrt the driver
                    MyEJDriver.StopDriver();
                    Server_State = -2;
                    if (Server_running == true)
                    {
                        myRemoteserver.setRecord<double>(PV_ServerState, Server_State);
                    }
                    DRV_ok = false;
                    ThreadReiniDrv = new Thread(new ThreadStart(ReIniDriver));
                    ThreadReiniDrv.Start();
                }
                //you should call
            }
            finally 
            {  
                //noting to do
            }
        }


        #region epics message

        private byte[] GetMsgByte()
        {
            //this will get the message arrary recorde
            //and turn it to byte
            sbyte [] msgSbyte;
            byte[] msgByte;
            int msgLen;
            msgLen = ((int)myRemoteserver.getRecord<double>(PV_MSG_Len));
            //msgSbyte = new sbyte[msgLen];
            msgSbyte = myRemoteserver.getRecordArray<sbyte>(PV_MSG_IN, 0, msgLen - 1);
            msgByte = Str_Short.SByteToByte(msgSbyte);
            return msgByte;
        }

        private string GetMsgString()
        {
            //this will get the message arrary recorde
            //and turn it to string
            sbyte [] msgSbyte;
            string msgStr;
            int msgLen;
            msgLen = ((int)myRemoteserver.getRecord<double>(PV_MSG_Len));
            //msgSbyte = new sbyte[msgLen];
            msgSbyte = myRemoteserver.getRecordArray<sbyte>(PV_MSG_IN,0,msgLen-1);
            msgStr = Str_Short.SByteToString(msgSbyte);
            return msgStr;
        }

        private int SetMsg(byte[] msg,sbyte[] client)
        {
            //this will set the message out to arrary recorde
            
            sbyte[] msgSbyte;
            double msgLen;
            msgSbyte = Str_Short.ByteToSByte(msg);
            msgLen = msgSbyte.Length;

            myRemoteserver.setRecord<double>(PV_MSG_Len_OUT, msgLen);   //set lent
            myRemoteserver.setRecordArray<sbyte>(PV_ClientName_Out, client, 0, 0, client.Length);   //set the name
            myRemoteserver.setRecordArray<sbyte>(PV_MSG_OUT, msgSbyte, 0, 0, msgSbyte.Length);
             
            return (int)msgLen;
        }



        #endregion


        /// <summary>
        /// indecates if the hardware connected to this EJServer is ok.
        /// you can use the pv : ServerName + ":SERVER_STATE" to monitor it
        /// </summary>
        /// <value>
        /// indecates if the hardware connected to this EJServer is ok.
        /// </value>
        public double SeverState
        {
            get { return Server_State; }
        }

        //use console to out put
        private void ConsoleOutput(string CMsg)
        {
            if (ConsoleOut == true)
            {
                Console.WriteLine(CMsg);
            }
        }

        private void HeartBeat()
        {
            //make the heart beating
            while (true)
            {
                Thread.Sleep(1000);
                if (Server_running == false)
                {
                    return;
                }
                if (HeartBeatVal < 100)
                {
                    HeartBeatVal += 1;
                }
                else
                {
                    HeartBeatVal = 1;
                }
                myRemoteserver.setRecord<int>(PV_HeartBeat, HeartBeatVal);
            }
        }


    }
}
